using System;
using System.Reflection;
using System.Configuration;
using System.Configuration.Provider;

using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;

using System.Security;
using System.Security.Principal;

namespace CSharpDistilled.Diagnostics.Providers
{
    /// <summary>
    /// Defines the contract that allows for the publication of exceptions through one or more custom exception providers. 
    /// </summary>
    public abstract class ExceptionProviderBase : ProviderBase
    {
        #region ExceptionProviderBase Private Fields

        private ExceptionContext m_context;

        #endregion

        #region protected ExceptionContext ExceptionContext

        /// <summary>
        /// Gets or sets the <see cref="T:CSharpDistilled.Diagnostics.ExceptionContext"/> containing details regarding the <see cref="T:System.Exception"/> to be published.
        /// </summary>
        protected ExceptionContext ExceptionContext
        {
            get
            {
                return m_context;
            }
            set
            {
                m_context = value;
            }
        }

        #endregion

        #region protected ExceptionProviderBase()

        /// <summary>
        /// Initializes a new instance of the <see cref="T:ExceptionProviderBase"/> class.    
        /// </summary>
        protected ExceptionProviderBase()
        {
            
        }

        #endregion

        #region public static ExceptionProviderBase CreateInstance()

        /// <summary>
        /// Creates an instance of the default exception provider.
        /// </summary>
        /// <returns>An instance of the default exception provider.</returns>
        [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        public static ExceptionProviderBase CreateInstance()
        {
            try
            {
                // retrieve the exception provider configuration.
                ProviderConfiguration configuration = ProviderConfiguration.GetConfiguration();

                // retrieve an instance of the default provider.
                Provider provider = configuration.ExceptionProviders[configuration.DefaultExceptionProvider] as Provider;

                #region Trace.Write()

                Trace.Write("ExceptionProviderBase.CreateInstance() - Default Exception Provider = ");
                Trace.WriteLine(provider.Name);
                Trace.Flush();

                #endregion

                // establish the System.Type for the default provider.
                Type providerType = Type.GetType(provider.Type);

                return Activator.CreateInstance(providerType) as ExceptionProviderBase;
            }
            catch(Exception ex)
            {
                Trace.WriteLine(ex.Message);
                Trace.WriteLine(ex.StackTrace);

                // no exception provider configuration could be found or specified provider was not found, return the default provider.
                return new SqlExceptionProvider() as ExceptionProviderBase;
            }
        }

        #endregion

        #region public abstract void Publish(Exception ex, IIdentity identity)

        /// <summary>
        /// Publishes the specified <see cref="T:System.Exception"/> using the configured custom exception provider.
        /// </summary>
        /// <param name="ex">The <see cref="T:System.Exception"/> to be published.</param>
        /// <param name="identity">The <see cref="T:System.Security.Principal.IIdentity"/> to associate with this exception.</param>
        public abstract void Publish(Exception ex, IIdentity identity);

        #endregion

        #region public abstract void Publish(object context)

        /// <summary>
        /// Publishes a <see cref="T:System.Exception"/> instance and associated contextual information.
        /// </summary>
        /// <param name="context">The <see cref="T:CSharpDistilled.ExceptionContext"/> to be published.</param>
        public abstract void Publish(object context);

        #endregion

        #region public static int? GetHResult(Exception ex)

        /// <summary>
        /// Gets the value of the <see cref="M:System.Exception.HResult"/> property.
        /// </summary>
        /// <param name="ex">The <see cref="T:System.Exception"/> for which the value of the <see cref="M:System.Exception.HResult"/> property is required.</param>
        /// <returns>The value of the <see cref="M:System.Exception.HResult"/> property within the supplied <see cref="T:System.Exception"/> instance.</returns>
        public static int? GetHResult(Exception ex)
        {
            Type _type = ex.GetType();

            PropertyInfo[] properties = _type.GetProperties(BindingFlags.GetProperty | BindingFlags.NonPublic | BindingFlags.Instance);

            foreach (PropertyInfo property in properties)
            {
                if (string.Equals(property.Name, "HResult"))
                {
                    return (int)property.GetValue(ex, null);
                }
            }

            #region Trace.WriteLine()

            Trace.WriteLine("CSharpDistilled::Distilled::Providers::SqlExceptionProvider::GetHResult()");
            Trace.Indent();
            Trace.WriteLine("Unable to determine HResult value for '");
            Trace.WriteLine(ex.GetType().ToString());
            Trace.WriteLine("'.");
            Trace.Unindent();

            Trace.Flush();

            #endregion

            return null;
        }

        #endregion

        #region public static bool? GetIsTransient(Exception ex)

        /// <summary>
        /// Gets the value of the <see cref="M:System.Exception.IsTransient"/> property.
        /// </summary>
        /// <param name="ex">The <see cref="T:System.Exception"/> for which the value of the <see cref="M:System.Exception.IsTransient"/> property is required.</param>
        /// <returns>The value of the <see cref="M:System.Exception.IsTransient"/> property within the supplied <see cref="T:System.Exception"/> instance.</returns>
        public static bool? GetIsTransient(Exception ex)
        {
            Type _type = ex.GetType();

            PropertyInfo[] properties = _type.GetProperties(BindingFlags.GetProperty | BindingFlags.NonPublic | BindingFlags.Instance);

            foreach (PropertyInfo property in properties)
            {
                if (string.Equals(property.Name, "IsTransient"))
                {
                    return (bool)property.GetValue(ex, null);
                }
            }

            #region Trace.WriteLine()

            Trace.WriteLine("CSharpDistilled::Distilled::Providers::SqlExceptionProvider::GetIsTransient()");
            Trace.Indent();
            Trace.WriteLine("Unable to determine IsTransient value for '");
            Trace.WriteLine(ex.GetType().ToString());
            Trace.WriteLine("'.");
            Trace.Unindent();

            Trace.Flush();

            #endregion

            return null;
        }

        #endregion
    }
}
